package redecolaborativa.server.sistema;




import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.net.URL;
import java.util.*;	

import redecolaborativa.server.excecoes.*;
import redecolaborativa.server.objetos.Emprestimo;
import redecolaborativa.server.objetos.Item;
import redecolaborativa.server.objetos.Pedido;
import redecolaborativa.server.util.Util;
import redecolaborativa.server.util.VerificadorDeOcorrencia;
import redecolaborativa.server.util.Visualizador;

/**
 * Classe que representa o gerenciador da rede colaborativa.
 * @author Isabelly Rocha, Lilia Sampaio e Pedro Adelino.
*/
public class Gerenciador implements Serializable {
	private static final long serialVersionUID = 1L;
	public static GregorianCalendar data =  new GregorianCalendar();
	private static Gerenciador gerenciador;
	private HashMap<String, Pedido> pedidos;
	private List<Usuario> usuarios;
	private List<Sessao> sessoesAbertas;
	
	private Gerenciador() {
		usuarios = new LinkedList<Usuario>();
		sessoesAbertas = new ArrayList<Sessao>();
		pedidos = new HashMap<String, Pedido>();
	}

	/**
	 * Reseta o gerenciador apagando os usuarios da lista de cadastrados e e as sessoes abertas da lista de sessoes.
	 */
	public void reset() {
		Emprestimo.resetaGeradorID();
		Item.resetaGeradorID();
		Sessao.resetaGeradorID();
		usuarios.clear();
		sessoesAbertas.clear();
		pedidos.clear();
	}
	
	/**
	 * Encerra o sistema gravando os dados coletados e contidos no gerenciador.
	 */
	public void encerrar() {
		Util.gravaDadosEmArquivo(gerenciador);
	}
	
	/**
	 * Recupera os usuarios do sistema.
	 * @return Uma lista com os usuarios do sistema.
	 */
	public List<Usuario> usuarios() {
		return new LinkedList<Usuario>(usuarios);
	}
	
	/**
	 * Recupera as sessoas abertas no sistema.
	 * @return Uma lista com as sessoes abertas no sistema.
	 */
	public List<Sessao> sessoesAbertas() {
		return new LinkedList<Sessao>(sessoesAbertas);
	}
	
	/**
	 * Recupera os pedidos
	 * @return Um mapa contendo os pedidos
	 */
	public HashMap<String, Pedido> pedidos() {
		return new HashMap<String, Pedido>(pedidos);
	}
	
	/**
	 * Abre uma sessao no sistema do usuario com o login passado, adicionando essa sessao a lista das abertas.
	 * @param login Login do usuario que deseja abrir a sessao.
	 * @return A sessao aberta.
	 * @throws UsuarioInexistenteException Caso o usuario nao exista no sistema.
	 * @throws LoginInvalidoException Caso o login seja nulo ou vazio.
	 */
	public Sessao abrirSessao(String login) throws UsuarioInexistenteException, LoginInvalidoException {
		if (login == null || login.trim().equals("")) {
			throw new LoginInvalidoException("Login inválido");
		} if (getUsuario(login) == null) {
			throw new UsuarioInexistenteException("Usuário inexistente");
		}
		Iterator<Usuario> it = usuarios.iterator();
		while (it.hasNext()) {
			Usuario usuario = it.next();
			if (usuario.getLogin().equals(login)) {
				Sessao sessao = new Sessao(usuario);
				sessoesAbertas.add(sessao);
				return sessao;
			}
		} return null;
	}
	
	/**
	 * Cadastra um usuario, adicionando a lista de usuario
	 * @param nome Nome do Usuario
	 * @param login Login do Usuario
	 * @param endereco Endereco doo Usuario
	 * @throws NomeInvalidoException Se o nome do usuario for nulo, vazio ou conter digitos
	 * @throws LoginInvalidoException Se o login do usuario for nulo ou vazio
	 * @throws EnderecoInvalidoException Se o endereco do usuario for nulo ou vazio
	 * 
	 */
	public void cadastrarUsuario(String nome, String login, String endereco) throws LoginInvalidoException, NomeInvalidoException, EnderecoInvalidoException {
		if (getUsuario(login) != null) {
			throw new LoginInvalidoException("Já existe um usuário com este login");
		} usuarios.add(new Usuario(nome, login, endereco));
	}
	
	/**
	 * Recupera o atributo de um usuario de um dado tipo passado no parametro.
	 * @param login Login do usuario que deseja o atributo.
	 * @param atributo Tipo do atributo buscado.
	 * @return Uma string com o atributo buscado.
	 * @throws LoginInvalidoException Caso o lgin seja nulo ou vazio.
	 * @throws AtributoInvalidoException Caso o atributo seja nulo ou vazio.
	 * @throws UsuarioInexistenteException Caso o usuario nao exista no sistema.
	 */
	public String getAtributoDeUmUsuario(String login, String atributo) throws LoginInvalidoException, AtributoInvalidoException, UsuarioInexistenteException {
		if (login == null || login.trim().equals("")) {
			throw new LoginInvalidoException("Login inválido");
		} if (atributo == null || atributo.trim().equals("")) {
			throw new AtributoInvalidoException("Atributo inválido");
		} if (getUsuario(login) == null) {
			throw new UsuarioInexistenteException("Usuário inexistente");
		} if (!atributo.equals("nome") && !atributo.equals("endereco")) {
			throw new AtributoInvalidoException("Atributo inexistente");
		}
		Iterator<Usuario> it = usuarios.iterator();
		while (it.hasNext()) {
			Usuario usuario = it.next();
			if (usuario.getLogin().equals(login)) {
				if (atributo.equals("nome")) {
					return usuario.getNome();
				} if (atributo.equals("endereco")) {
					return usuario.getEndereco();
				}
			}
		} return "";
	}
	
	
	/** Localiza um usuario com o idSessao especificado e o com o login especificado.
	 * @param idSessao Id do usuario logado.
	 * @param login Login do usuario procurado.
	 * @return Uma lista com ambos os usuarios, logado e procurado, nessa ordem.
	 * @throws SessaoInvalidaException Caso o idSessao seja nulo ou vazio ou nao exista no sistema.
	 * @throws LoginInvalidoException Caso o login seja nulo ou vazio ou nao exista no sistema.
	 */
	public List<Usuario> localizaUsuariosLogadoEProcurado(String idSessao, String login) throws SessaoInvalidaException, LoginInvalidoException {
		if (idSessao == null || idSessao.trim().equals("")) {
			throw new SessaoInvalidaException("Sessão inválida");
		} if (!sessaoExiste(idSessao)) {
			throw new SessaoInvalidaException("Sessão inexistente");
		} if(login == null || login.trim().equals("")) {
			throw new LoginInvalidoException("Login inválido");
		} if(getUsuario(login) == null) {
			throw new LoginInvalidoException("Usuário inexistente");
		}
		List<Usuario> logadoEProcurado = new ArrayList<Usuario>();
		Usuario procurado = getUsuario(login);
		Iterator<Sessao> it = sessoesAbertas.iterator();
		while (it.hasNext()) {
			Sessao sessao = it.next();
			if (String.valueOf(sessao.getId()).equals(idSessao) && procurado != null) {
				logadoEProcurado.add(sessao.getUsuario()); 
				logadoEProcurado.add(procurado);
			}
		} return logadoEProcurado;
	}
	
	/**
	 * Metodo que localiza o remetente e o destinatario
	 * @param remetente O Remetente
	 * @param destinatario O Destinatario
	 * @return Retorna uma lista contendo o remetente e o destinatario
	 * @throws SessaoInvalidaException
	 * @throws UsuarioInexistenteException
	 */
	public List<Usuario> localizaRemetenteDestinatario(String remetente, String destinatario) throws SessaoInvalidaException, UsuarioInexistenteException {
		if (remetente == null || remetente.trim().equals("")) {
			throw new SessaoInvalidaException("Sessão inválida");
		} if (!sessaoExiste(remetente)) {
			throw new SessaoInvalidaException("Sessão inexistente");
		} if (destinatario == null || destinatario.trim().equals("")) {
			throw new UsuarioInexistenteException("Destinatário inválido");
		} if(getUsuario(destinatario) == null) {
			throw new UsuarioInexistenteException("Destinatário inexistente");
		}
		List<Usuario> remetenteDest = new ArrayList<Usuario>();
		Usuario dest = getUsuario(destinatario);
		Iterator<Sessao> it = sessoesAbertas.iterator();
		while (it.hasNext()) {
			Sessao sessao =  it.next();
			if(String.valueOf(sessao.getId()).equals(remetente) && dest != null) {
				remetenteDest.add(sessao.getUsuario()); remetenteDest.add(dest);
			}
		} return remetenteDest;
	}
	
	/**
	 * Localiza um usuario dada uma certa chave, e o atributo ao qual a chave se refere.
	 * @param idLogado ID do usuario logado na sessao.
	 * @param chave Chave a ser pesquisada.
	 * @param atributo Atributo ao qual a chave se refere.
	 * @return Uma string com os resultados da pesquisa.
	 * @throws SessaoInvalidaException Se a sessoa do usuario logado nao existir no sistema.
	 * @throws PalavraChaveInvalidaException Se a palavra chave for nula ou vazia.
	 * @throws AtributoInvalidoException Se o atributo for nulo ou vazio.
	 */
	public String localizaUsuario(String idLogado, String chave, String atributo) throws SessaoInvalidaException, PalavraChaveInvalidaException, AtributoInvalidoException {
		if (chave == null || chave.trim().equals("")) {
			throw new PalavraChaveInvalidaException("Palavra-chave inválida");
		} if (atributo == null || atributo.trim().equals("")) {
			throw new AtributoInvalidoException("Atributo inválido");
		} if (!atributo.equals("nome") && !atributo.equals("endereco")) {
			throw new AtributoInvalidoException("Atributo inexistente");
		}
		String retorno = "";
		if(atributo.equals("nome")) {
			retorno = localizarUsuarioPorNome(idLogado, chave);
		} else if(atributo.equals("endereco")) {
			retorno = localizarUsuarioPorEndereco(idLogado, chave);
		} if(retorno.equals("")) {
			retorno = "Nenhum usuário encontrado";
		} return retorno;
	}
	
	/**
	 * Localiza usuarios pelo seu nome.
	 * @param Nome do usuario
	 * @return A lista de usuarios localizados
	 * @throws SessaoInvalidaException 
	 */
	private String localizarUsuarioPorNome(String idLogado, String nome) throws SessaoInvalidaException { 
		Usuario logado = localizaUsuarioPorSessao(idLogado);
		List<Usuario> usuariosEncontrados = new LinkedList<Usuario>();
		Iterator<Usuario> it = usuarios.iterator();
		while (it.hasNext()) {
			Usuario usuario = it.next();
			if (!usuario.equals(logado) && (VerificadorDeOcorrencia.ocorre(usuario.getNome(), nome) || VerificadorDeOcorrencia.ocorre(usuario.getLogin(), nome))) {
				usuariosEncontrados.add(usuario);
			}
		}
		ordena(logado, usuariosEncontrados);
		return Visualizador.imprimePesquisaUsuario(usuariosEncontrados);
	}
	
	/**
	 * Localiza usuarios pelo endereco
	 * @param Endereco do usuario
	 * @return A lista de usuario localizados
	 * @throws SessaoInvalidaException 
	 */
	private String localizarUsuarioPorEndereco(String idLogado, String endereco) throws SessaoInvalidaException { 
		Usuario logado = localizaUsuarioPorSessao(idLogado);
		List<Usuario> usuariosEncontrados = new LinkedList<Usuario>();
		Iterator<Usuario> it = usuarios.iterator();
		while (it.hasNext()) {
			Usuario usuario = it.next();
			if (!usuario.equals(logado) && VerificadorDeOcorrencia.ocorre(usuario.getEndereco(), endereco)) {
				usuariosEncontrados.add(usuario);
			}
		}
		ordena(logado, usuariosEncontrados);
		return Visualizador.imprimePesquisaUsuario(usuariosEncontrados);
	}
	
	/**
	 * Localiza um usuario logado no sistema dado o ID da sessao.
	 * @param idSessao ID da sessao do usuario logado.
	 * @return O usuario logado no sistema na dada sessao.
	 * @throws SessaoInvalidaException Se a sessao for nula ou vazia, ou nao existir no sistema.
	 */
	public Usuario localizaUsuarioPorSessao(String idSessao) throws SessaoInvalidaException {
		if (idSessao == null || idSessao.trim().equals("")) {
			throw new SessaoInvalidaException("Sessão inválida");
		} if (!sessaoExiste(idSessao)) {
			throw new SessaoInvalidaException("Sessão inexistente");
		} 
		Iterator<Sessao> it = sessoesAbertas().iterator();
		while (it.hasNext()) {
			Sessao sessao = it.next();
			if (idSessao.equals(String.valueOf(sessao.getId()))) {
				return sessao.getUsuario();
			}
		} return null;
	}
	
	/**
	 * Adiciona um pedido no mapa de pedidos
	 * @param usuario Usuario que faz o pedido
	 * @param nomeItem Item pedido
	 * @return Id do pedido
	 */
	public String adicionarPedido(Usuario usuario, String nomeItem) {
		pedidos.put((Integer.toString(pedidos.size()+1)), new Pedido(usuario, nomeItem));
		return Integer.toString(pedidos.size());
	}
	
	/**
	 * Retorna uma string contendo o ranking (global ou entre amigos) dos usuarios do sistema a partir dos emprestimos completados por eles. 
	 * @param categoria Categoria do ranking, global ou entre amigos.
	 * @param logado Usuario que esta logado no sistema.
	 * @return Uma string contendo o ranking dos usuarios pelos emprestimos completados.
	 * @throws CategoriaInvalidaException Se a categoria for nula, vazia ou inexistente.
	 */
	public String ranking(String categoria, Usuario logado) throws CategoriaInvalidaException {
		if(categoria == null || categoria.trim().equals("")) {
			throw new CategoriaInvalidaException("Categoria inválida");
		}  if (!categoria.trim().equals("global") && !categoria.trim().equals("amigos")) {
			throw new CategoriaInvalidaException("Categoria inexistente");
		} if(categoria.trim().equals("global")) {
			return rankingGlobal();
		} return rankingEntreAmigos(logado);
	}
	
	/**
	 * Retorna um ranking com todos os usuarios do sistema de acordo com o numero de emprestimos finalizados de cada um. 
	 * @return Uma string com o ranking dos usuarios do sistema de acordo com o numero de emprestimos finalizados.
	 */
	public String rankingGlobal() {
		return ranking(usuarios);
	}
	
	/**
	 * Retorna um ranking com o o usuario e seus amigos de acordo com o numero de emprestimos finalizados.
	 * @param logado Usuario logado que deseja ver o ranking entre ele e os amigos.
	 * @return Uma strin com o ranking do usuario e seus amigos de acordo com o numero de emprestimos finalizados.
	 */
	public String rankingEntreAmigos(Usuario logado) {
		List<Usuario> amigos = new LinkedList<Usuario>();
		amigos.add(logado);
		Iterator<Usuario> it = usuarios().iterator();
		while (it.hasNext()) {
			Usuario usuario =  it.next();
			if (logado.gerenteDeAmigos().contem(usuario)) {
				amigos.add(usuario);
			}
		} return ranking(amigos);
	}
	
	/**
	 * Verifica se uma mensagem existe
	 * @param idTopico Id topico da mensagem
	 * @return True caso exista, ou false caso contrario
	 */
	public boolean mensagemExiste(String idTopico) {
		Iterator<Usuario> it = usuarios().iterator();
		while (it.hasNext()) {
			Usuario usuario = it.next();
			if (possuiMensagem(usuario, idTopico)) {
				return true;
			}
		} return false;
	}
		
	/**
	 * Metodo que verifica se um Usuario pode ler a mensagem
	 * @param fulano Usuario que quer ler a mensagem
	 * @param idTopico Id do topico da mensagem
	 * @return True caso possa ler, ou False caso contrario
	 */
	public boolean podeLerMensagem(Usuario fulano, String idTopico) {
		Iterator<Usuario> it = usuarios.iterator();
		while (it.hasNext()) {
			Usuario usuario = it.next();
			if (possuiMensagem(usuario, idTopico) && (!fulano.gerenteDeAmigos().contem(usuario)) && !usuario.equals(fulano)) {
				return false;
			}
		} return true;
	}
	
	/**
	 * Metodo que oferece um item
	 * @param fulano Usuario que oferece o item
	 * @param nomeUsuario Usuario ao qual eh oferecido o item
	 * @param nomeItem Nome do item oferecido
	 * @throws Exception
	 */
	public void oferecerItem(Usuario fulano, String idItem, String idPublicacaoPedido) throws Exception {
		if (!pedidos().containsKey(idPublicacaoPedido)) {
			throw new Exception("Publicação de pedido inexistente");
		} if (fulano.gerenteDeItens().pesquisaItemPorID(idItem) == null) {
			throw new Exception("Item inexistente");
		}
		String publicador = pedidos().get(idPublicacaoPedido).getUsuario().getNome();
		String nomeItem = fulano.gerenteDeItens().pesquisaItemPorID(idItem).getNome();
		Iterator<Usuario> it = usuarios.iterator();
		while (it.hasNext()) {
			Usuario usuario = it.next();
			if (usuario.getNome().equals(publicador)) {
				fulano.oferecerItem(usuario, nomeItem);
			}
		}
	}
	
	/**
	 * Implementacao do Padrao Singleton
	 * Recupera a instancia do gerenciador se ela ja existir, ou cria uma caso contrario.
	 * @return A instancia do gerenciador.
	 */
	public static Gerenciador getInstance() {
		if (Util.recuperaDadosDeArquivo() == null) {
			Util.gravaDadosEmArquivo(new Gerenciador());
		} return gerenciador = Util.recuperaDadosDeArquivo();
	}
	
	/**
	 * Adiciona uma determinada quantidade de dias a data do sistema.
	 * @param Dias a serem acrescentados
	 */
	public void adicionarDias(int dias) {
		data.add(Calendar.DAY_OF_YEAR, dias);
	}
	
	/**
	 * Mostra os usuarios ordenados pela distancia
	 * @param idSessao Id da sessao do Usuario base da pesquisa
	 * @return Uma string contendo os usuarios pela ordem de distancia
	 * @throws Exception
	 */
	public String usuariosOrdenadosPelaDistancia(String idSessao) throws Exception {
		if (idSessao == null || idSessao.trim().equals("")) {
			throw new Exception("Sessão inválida");
		} if (localizaUsuarioPorSessao(idSessao) == null) {
			throw new Exception("Sessão inexistente");
		}
		Usuario logado = localizaUsuarioPorSessao(idSessao);
		List<Usuario> outrosUsuarios = new ArrayList<Usuario>(usuarios);
		outrosUsuarios.remove(logado);
		ordena(logado, outrosUsuarios);
		return Visualizador.imprimePesquisaUsuario(outrosUsuarios);
	}

	/*
	 * Constroi uma string com o ranking dos usuarios, auxiliando o metodo rankingGlobal e rankingEntreAmigos.
	 */
	private String ranking(List<Usuario> usuarios) {
		Map<Integer, List<Usuario>> ranking = new HashMap<Integer, List<Usuario>>();
		Iterator<Usuario> it = usuarios.iterator();
		while (it.hasNext()) {
			Usuario usuario = it.next();
			if (ranking.containsKey(usuario.gerenteDeEmprestimos().emprestimosFinalizados())) {
				ranking.get(usuario.gerenteDeEmprestimos().emprestimosFinalizados()).add(usuario);
			} else {
				List<Usuario> lista = new LinkedList<Usuario>();
				lista.add(usuario);
				ranking.put(usuario.gerenteDeEmprestimos().emprestimosFinalizados(), lista);
			}
		} return Visualizador.imprimeRanking(ranking);
	}
	
	/*
	 * Verifica se um usuario existe no sistema.
	 */
	private Usuario getUsuario(String login) {
		Iterator<Usuario> it = usuarios.iterator();
		while (it.hasNext()) {
			Usuario usuario = it.next();
			if(usuario.getLogin().equals(login)) {
				return usuario; 
			}
		} return null;
	}

	/*
	 * Verifica se uma sessao existe no sistema.
	 */
	private boolean sessaoExiste(String idSessao) {
		Iterator<Sessao> it = sessoesAbertas().iterator();
		while (it.hasNext()) {
			Sessao sessao = it.next();
			if (String.valueOf(sessao.getId()).equals(idSessao)) {
				return true;
			}
		} return false;
	}
	
	/*
	 * Verifica se um usuario possui certa mensagem
	 */
	private boolean possuiMensagem(Usuario usuario, String idTopico) {
		try {
			Visualizador.vizualizaMensagensPorId(usuario, idTopico);
			return true;
		} catch (Exception ex) { 
			return false; 
		}
	}
	
	/**
	 * Metodo que retorna os usuarios e seus logins
	 * @return String contendo os usuarios e os logins
	 */
	public String retornaUsuariosESeusLogins(){
		String retorno = "";
		for(Usuario usuario : usuarios()){
			retorno += String.format("Usuario: %s | Login: %s \n", usuario.getNome(), usuario.getLogin());
		} return retorno;
	}  
	
	/**
	 * Retorna um atributo do item atravez de uma pesquisa
	 * @param idItem Id do Item
	 * @param atributo Nome, descricao ou categoria
	 * @return String contendo o resultado da pesquisa
	 * @throws Exception
	 */
	public String getAtributoItem(String idItem, String atributo) throws Exception  {
		String retorno = "";
		for (Usuario usuario : usuarios()) {
			Item item = usuario.gerenteDeItens().pesquisaItemPorID(idItem);
			if(item != null) {
				retorno = item.getAtributo(atributo);
			}
		} if (retorno.equals("")) {
			throw new ItemInvalidoException("Item inexistente");
		}
		return retorno;
	}
	
	/**
	 * Retorna o id do item
	 * @param nomeItem Nome do item
	 * @return Uma string informando o id do item
	 * @throws Exception
	 */
	public String localizaItemPorNome(String nomeItem) throws Exception {
		Item itemProc = null;
		for (Usuario usuario : usuarios()) {
			itemProc = usuario.gerenteDeItens().pesquisaItemPorNome(nomeItem);
			if(itemProc != null) {
				return String.valueOf(itemProc.getID());
			}
		} throw new ItemInvalidoException("Item inexistente");
	}

	/**
	 * Metodo que localiza item por id
	 * @param idItem Id do item
	 * @return O item encontrado
	 * @throws ItemInvalidoException
	 */
	public Item localizaItemPorID(String idItem) throws ItemInvalidoException {
		Item itemProc = null;
		for (Usuario usuario : usuarios) {
			itemProc = usuario.gerenteDeItens().pesquisaItemPorID(idItem);
			if(itemProc != null) {
				return itemProc;
			}
		} throw new ItemInvalidoException("Item inexistente");
	}
	
	/**
	 * Pesquisa uma requisicao de emprestimo pelo seu ID.
	 * @param idRequisicaoEmprestimo ID da requisicao de um emprestimo.
	 * @return O emprestimo pesquisado.
	 * @throws RequisicaoInvalidaException Se o ID da requisicao for nulo ou vazio.
	 */
	public Emprestimo pesquisaRequisicaoEmprestimoId(String idRequisicaoEmprestimo) throws RequisicaoInvalidaException {
		if(idRequisicaoEmprestimo == null || idRequisicaoEmprestimo.trim().equals("")) {
			throw new RequisicaoInvalidaException("Identificador da requisição de empréstimo é inválido");
		} 
		for(Usuario usuario: usuarios) {
			for(Emprestimo emprestimo : usuario.gerenteDeEmprestimos().getSolicitacoesDeEmprestimo()) {
				if(String.valueOf(emprestimo.getIDRequisicao()).equals(idRequisicaoEmprestimo)) {
					return emprestimo;
				}
			} 
		} throw new RequisicaoInvalidaException("Requisição de empréstimo inexistente");
	}
	
	/**
	 * Pesquisa emprestimo pelo seu id
	 * @param usuario Usuario
	 * @param idEmprestimo Id do emprestimo a ser pesquisado
	 * @return O emprestimo pesquisado
	 * @throws RequisicaoInvalidaException
	 */
	public Emprestimo pesquisaEmprestimoPorId(String idEmprestimo) throws RequisicaoInvalidaException{
		if(idEmprestimo == null || idEmprestimo.trim().equals("")) {
			throw new RequisicaoInvalidaException("Identificador do empréstimo é inválido");
		}
		for(Usuario usuario: usuarios) {
			for(Emprestimo emprestimo : usuario.gerenteDeEmprestimos().getAdquiridos()) {
				if(String.valueOf(emprestimo.getIDEmprestimo()).equals(idEmprestimo)) {
					return emprestimo;
				}
			} 
		} throw new RequisicaoInvalidaException("Empréstimo inexistente");
	}	
	
	/**
	 * Ordena a lista de usuario pela distancia
	 * @param usuario Usuario base da pesquisa
	 * @param lista Lista de usuario que vai ser comparado com o usuario base da pesquisa
	 * @return A lista com os usuarios em ordem.
	 */
	private void ordena(Usuario usuario, List<Usuario> lista) {
		final float[] latLngUsuario = latitudeLongitude(usuario.getEndereco());
		Collections.sort(lista, new Comparator<Usuario>() {
			@Override public int compare(Usuario a, Usuario b) {
			float[] latLngU1 = latitudeLongitude(a.getEndereco()); 
			float[] latLngU2 = latitudeLongitude(b.getEndereco());
			return Float.valueOf(distanciaEntrePontos(latLngUsuario[0], latLngUsuario[1], latLngU1[0], latLngU1[1])).
				   compareTo(distanciaEntrePontos(latLngUsuario[0], latLngUsuario[1], latLngU2[0], latLngU2[1]));}});
	}
	
	/**
	 * Calcula a distancia entre dois pontos usando a longitude e a latitude
	 * @param lat1 Latitude de um ponto
	 * @param lng1 Longitude de um ponto
	 * @param lat2 Latitude do outro ponto
	 * @param lng2 Longitude do outro ponto
	 * @return A distancia em metros
	 */
	private static float distanciaEntrePontos(float lat1, float lng1, float lat2, float lng2) { 
		double earthRadius = 3958.75; 
		double dLat = Math.toRadians(lat2-lat1); 
		double dLng = Math.toRadians(lng2-lng1); 
		double a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * Math.sin(dLng/2) * Math.sin(dLng/2); 
		double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
		double dist = earthRadius * c; 
		int meterConversion = 1609; 
		return new Float(dist * meterConversion).floatValue(); 
	 }
	
	/**
	 * Retorna um array de float onde o elemento da primeira posicao é a latitude e o da segunda posicao é a
	 * longitude do endereco passado no parametro.
	 * @param Endereco
	 * @return Array contendo a latitude e a longitude de um dado endereco.
	 */
	private float[] latitudeLongitude(String endereco) {
		float[] latitudeLongetude = new float[2];
		try {
			URL url = new URL("http://maps.google.com/maps/api/geocode/json?address=" + formataEndereco(endereco) + "&sensor=true");
	        BufferedReader buffer = new BufferedReader(new InputStreamReader(url.openStream()));
	        String linha;
	        while ((linha = buffer.readLine()) != null) {
	        	if (latitudeLongetude[0] != 0 && latitudeLongetude[1] != 0) {
	        		break;
	        	} if (linha.contains("lat") && latitudeLongetude[0] == 0) {
	            	latitudeLongetude[0] = Float.parseFloat(linha.split(":")[1]);
	            } if (linha.contains("lng") && latitudeLongetude[1] == 0) {
	            	latitudeLongetude[1] = Float.parseFloat(linha.split(":")[1]);
	            }
	        } buffer.close();
		} catch (Exception e) { }
		return latitudeLongetude;
	}
	
	/**
	 * Deixa o endereco em um formto adequado para usar na api do google maps.
	 * @param Endereco
	 * @return Endereco formatado
	 */
	private String formataEndereco(String endereco) {
		String retorno = "";
        for(String string: endereco.split(" ")) {
            if (endereco.endsWith(string)) {
            	retorno += string;
            } else {
            	retorno += (string + "+"); 
            }
        } return retorno.trim();
	}
}